/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* common headers */
#include "common.h"

/* interface header */
#include "DXJoystick.h"

#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")

/* system headers */
#include <vector>
#include <string>
#include <map>
#include <stdlib.h>

/* local impl. headers */
#ifdef HAVE_SDL
#  include "SDL2Window.h"
#else
#  include "WinWindow.h"
#endif
#include "ErrorHandler.h"
#include "TextUtils.h"

std::vector<DIDEVICEINSTANCE> DXJoystick::devices;
std::map<std::string, LPDIRECTINPUTEFFECT> DXJoystick::effectDatabase;

DXJoystick::DXJoystick() : device(NULL)
{
    HINSTANCE hinst = GetModuleHandle(NULL);
    HRESULT success = DirectInput8Create(hinst, DIRECTINPUT_VERSION, IID_IDirectInput8,
                                         (void**)&directInput, NULL);

    if (success != DI_OK)
    {
        DXError("Could not initialize DirectInput", success);
        return;
    }
    numberOfHats = 0;
    enumerateDevices();
}

DXJoystick::~DXJoystick()
{
    // unacquire the joystick
    if (device)
        device->Unacquire();

    // release DX objects
    if (device)
    {
        device->Release();
        device = NULL;
    }
    if (directInput)
    {
        directInput->Release();
        directInput = NULL;
    }
}

BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )
{
    DXJoystick *stick = ( DXJoystick * )pContext;

    if ( pdidoi->guidType == GUID_POV )
        stick->numberOfHats++;

    return DIENUM_CONTINUE;
}

void          DXJoystick::initJoystick(const char* joystickName)
{
    // turn it off
    if (!joystickName || strcasecmp(joystickName, "off") == 0)
    {
        device = NULL;
        return;
    }

    /*
     * Find this device, and try to initialize it.
     */
    GUID thisDevice;
    for (unsigned int i = 0; i < devices.size(); i++)
    {
        if (strcmp(joystickName, devices[i].tszProductName) == 0)
        {
            thisDevice = devices[i].guidInstance;
            break;
        }
    }
    HRESULT success = directInput->CreateDevice(thisDevice,&device, NULL);

    if (success != DI_OK)
    {
        DXError("Could not initialize device", success);
        return;
    }

    /*
     * Set device cooperation level - all input on this device goes to BZFlag,
     * because we're greedy.
     */
#ifdef HAVE_SDL
    success = device->SetCooperativeLevel(SDLWindow::getHandle(),
                                          DISCL_BACKGROUND | DISCL_EXCLUSIVE);
#else
    success = device->SetCooperativeLevel(WinWindow::getHandle(),
                                          DISCL_BACKGROUND | DISCL_EXCLUSIVE);
#endif

    if (success != DI_OK)
    {
        // couldn't grab device, what to do now?
        DXError("Could not set exclusive mode", success);
        device = NULL;
        return;
    }


    // enumerate the axes and stuff
    success = device->EnumObjects(EnumObjectsCallback,(VOID*)this, DIDFT_POV);

    if (success != DI_OK)
    {
        // couldn't grab device, what to do now?
        DXError("Could not enumerate options", success);
        device = NULL;
        return;
    }

    /*
     * Set the device data format.  We want buttons and axis, so we'll use a
     * predefined Joystick structure.
     */

    success = device->SetDataFormat(&c_dfDIJoystick);

    if (success != DI_OK)
    {
        // couldn't set data format, what to do now?
        DXError("Could not set data format", success);
        device = NULL;
        return;
    }

    /*
     * Set data ranges for all axes, and find out which ones succeeded.
     * This has the side effect of ensuring that the joystick has at least two axes.
     */

    // we assume the presence of an X and Y axis and bail if there's not one
    axes["X"]  = true;
    xAxis = "X";
    axes["Y"]  = true;
    yAxis = "Y";
    // the rest we assume don't exist, and correct ourselves if we're wrong
    axes["Z"]  = false;
    axes["Rx"] = false;
    axes["Ry"] = false;
    axes["Rz"] = false;
    axes["Slider1"] = false;
    axes["Slider2"] = false;

    DIPROPRANGE range;
    range.diph.dwSize       = sizeof(range);
    range.diph.dwHeaderSize = sizeof(range.diph);
    range.diph.dwHow    = DIPH_BYOFFSET;
    range.lMin          = -1000;
    range.lMax          = +1000;

    range.diph.dwObj = DIJOFS_X;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success != DI_OK)
    {
        // couldn't set x axis range, what to do now?
        DXError("Could not set X-axis range", success);
        device = NULL;
        return;
    }

    range.diph.dwObj = DIJOFS_Y;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success != DI_OK)
    {
        // check out the sliders and see if we can map one of them to Y
        // this little trick should allow most wheels to work out of the box
        range.diph.dwObj = DIJOFS_SLIDER(0);
        success = device->SetProperty(DIPROP_RANGE, &range.diph);
        if (success == DI_OK)
            yAxis = "Slider 1";
        else
        {
            // couldn't set y axis range, what to do now?
            DXError("Could not set Y-axis range", success);
            device = NULL;
            return;
        }
    }

    range.diph.dwObj = DIJOFS_Z;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Z"] = true;

    range.diph.dwObj = DIJOFS_RX;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Rx"] = true;

    range.diph.dwObj = DIJOFS_RY;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Ry"] = true;

    range.diph.dwObj = DIJOFS_RZ;
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Rz"] = true;

    range.diph.dwObj = DIJOFS_SLIDER(0);
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Slider 1"] = true;

    range.diph.dwObj = DIJOFS_SLIDER(1);
    success = device->SetProperty(DIPROP_RANGE, &range.diph);
    if (success == DI_OK)
        axes["Slider 2"] = true;

    hataxes.assign(numberOfHats * 2, 0); // two axes each

    /*
     * Acquire the device so that we can get input from it.
     */

    reaquireDevice();
}

bool          DXJoystick::joystick() const
{
    return (device != NULL);
}

void          DXJoystick::getJoy(int& x, int& y)
{
    if (!device) return;

    DIJOYSTATE state = pollDevice();

    if (xAxis == "X")  x = state.lX;
    else if (xAxis == "Y")  x = state.lY;
    else if (xAxis == "Z")  x = state.lZ;
    else if (xAxis == "Rx") x = state.lRx;
    else if (xAxis == "Ry") x = state.lRy;
    else if (xAxis == "Rz") x = state.lRz;
    else if (xAxis == "Slider 1") x = state.rglSlider[0];
    else if (xAxis == "Slider 2") x = state.rglSlider[1];

    if (yAxis == "X")  y = state.lX;
    else if (yAxis == "Y")  y = state.lY;
    else if (yAxis == "Z")  y = state.lZ;
    else if (yAxis == "Rx") y = state.lRx;
    else if (yAxis == "Ry") y = state.lRy;
    else if (yAxis == "Rz") y = state.lRz;
    else if (yAxis == "Slider 1") y = state.rglSlider[0];
    else if (yAxis == "Slider 2") y = state.rglSlider[1];

    // ballistics
    x = (x * abs(x)) / 1000;
    y = (y * abs(y)) / 1000;

    return;
}

unsigned long DXJoystick::getJoyButtons()
{
    if (!device) return 0;

    DIJOYSTATE state = pollDevice();

    unsigned long buttons = 0;

    for (int i = 0; i < 32; i++)
    {
        if (state.rgbButtons[i] & 0x80)
            buttons |= (1 << i);
    }

    return buttons;
}

int    DXJoystick::getNumHats()
{
    return numberOfHats;
}

void      DXJoystick::getJoyHat(int hat, float &hatX, float &hatY)
{
    DIJOYSTATE state = pollDevice();

    for (int i = 0; i < numberOfHats; i++)
    {
        DWORD hatPos = state.rgdwPOV[i];

        float hatX = 0;
        float hatY = 0;
        BOOL hatCentered = (LOWORD(hatPos) == 0xFFFF);
        if (hatCentered)
        {
            hataxes[i * 2]     = hatX = 0;
            hataxes[i * 2 + 1] = hatY = 0;
        }
        else
        {
            // hatPos is indicated clockwise from north, we transform it so it
            // can be fed into sinf() and cosf() which start counting from east
            float angle = (float)hatPos/100.0f - 90;

            hataxes[i * 2]     = hatX = cosf(angle * DEG2RADf);
            hataxes[i * 2 + 1] = hatY = sinf(angle * DEG2RADf);
        }
    }

    hatX = hatY = 0;
    if (!device) return;
    if (hat >= numberOfHats) return;
    hatX = hataxes[hat * 2];
    hatY = hataxes[hat * 2 + 1];
}

DIJOYSTATE    DXJoystick::pollDevice()
{
    DIJOYSTATE state;

    HRESULT success = device->Poll();
    // umm, ignore that result...yeah

    success = device->GetDeviceState(sizeof(DIJOYSTATE), &state);
    if (success != DI_OK)
    {
        // got no state, what's wrong?
        DXError("Acquisition succeeded, but could not get joystick status", success);
    }

    return state;
}

void          DXJoystick::getJoyDevices(std::vector<std::string> &list) const
{
    for (unsigned int i = 0; i < devices.size(); i++)
        list.push_back(devices[i].tszProductName);
}

void          DXJoystick::getJoyDeviceAxes(std::vector<std::string> &list) const
{
    list.clear();
    std::map<std::string,bool>::const_iterator itr = axes.begin();
    while (itr != axes.end())
    {
        if (itr->second == true)
            list.push_back(itr->first);
        ++itr;
    }
}

void          DXJoystick::setXAxis(const std::string &axis)
{
    if (axes[axis] == false) return;
    xAxis = axis;
}

void          DXJoystick::setYAxis(const std::string &axis)
{
    if (axes[axis] == false) return;
    yAxis = axis;
}

/*
 * Cool force feedback functions.
 */

bool          DXJoystick::ffHasRumble() const
{
    if (!device)
        return false;

    DIDEVCAPS caps;
    caps.dwSize = sizeof(DIDEVCAPS);

    HRESULT success = device->GetCapabilities(&caps);

    if (success != DI_OK)
    {
        // couldn't get capabilities, assume no force feedback
        printError("Could not get joystick capabilities, assuming no force feedback");
        return false;
    }

    // if we support force feedback, assume we support rumble
    if (caps.dwFlags & DIDC_FORCEFEEDBACK)
        return true;

    return false;
}

void          DXJoystick::ffRumble(int count, float delay, float duration,
                                   float strong_motor, float weak_motor)
{
    if (!ffHasRumble())
        return;

    /*
     * Create a constant "rumbling" effect with the specified parameters
     * Note that on joysticks that support "real" force feedback this will
     * probably just feel like a constant pressure.
     */
    DICONSTANTFORCE constantForce;

    /* This is about consistent with the relative strength of the motors
     * in the Logitech Cordless Rumblepad, which seems to be what the Linux
     * FF_RUMBLE API (which bzflag's is patterned after) was designed for.
     */
    float combined = strong_motor + weak_motor / 2.0f;
    if (combined > 1.0f)
        combined = 1.0f;

    constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * combined);

    HRESULT success = DI_OK;

    // Generate a string to identify a specific rumble effect,
    // based on the parameters of the rumble
    std::string effectType = TextUtils::format("R%d|%d|%d|%d|%d", count, delay, duration, strong_motor, weak_motor);

    // Check if we need to create the effect
    EffectMap::iterator itr = effectDatabase.find(effectType);
    if (itr == effectDatabase.end())
    {

        /*
         * Wasn't in effect database, so build it
         */
        DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
        LONG  dir[2] = {1, 1};

        LPDIRECTINPUTEFFECT createdEffect = NULL;

        DIEFFECT effect;
        effect.dwSize = sizeof(DIEFFECT);
        // coordinate system really doesn't matter for rumbles but we need to specify it.
        effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        // duration
        effect.dwDuration = (DWORD)(duration * DI_SECONDS);
        // defaults
        effect.dwSamplePeriod = 0;
        effect.dwGain = DI_FFNOMINALMAX;
        effect.dwTriggerButton = DIEB_NOTRIGGER;
        effect.dwTriggerRepeatInterval = 0;
        // x and y axes
        effect.cAxes = 2;
        effect.rgdwAxes = &axes[0];
        // direction doesn't matter
        effect.rglDirection = &dir[0];
        // no envelope
        effect.lpEnvelope = NULL;
        // use the constant force data
        effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
        effect.lpvTypeSpecificParams = &constantForce;
        // start delay
        effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);

        // create the effect
        success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);

        if ((success != DI_OK) || (createdEffect == NULL))
        {
            DXError("Could not create rumble effect", success);
            return;
        }

        // Store the effect for later use
        effectDatabase[effectType] = createdEffect;
    }

    // play the thing
    if (effectDatabase[effectType])
        success = effectDatabase[effectType]->Start(count, 0);

    if (success != DI_OK)
    {
        // uh-oh, no worky
        DXError("Could not play rumble effect", success);
    }

    return;
}

void    DXJoystick::ffDirectionalConstant(int count, float delay, float duration,
        float x_direction, float y_direction,
        float strength)
{
    if (!ffHasDirectional())
        return;

    /*
     * Create a constant effect with the specified parameters
     */
    DICONSTANTFORCE constantForce;

    constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * strength);

    HRESULT success = DI_OK;

    // Generate a string to identify a specific constant effect,
    // based on the parameters of the effect
    std::string effectType = TextUtils::format("C%d|%d|%d|%d|%d|%d", count, delay, duration, x_direction, y_direction,
                             strength);

    // Check if we need to create the effect
    EffectMap::iterator itr = effectDatabase.find(effectType);
    if (itr == effectDatabase.end())
    {

        /*
         * Wasn't in effect database, so build it
         */
        DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
        LONG  dir[2] = {(int)(1000.0f * x_direction),
                        (int)(1000.0f * y_direction)
                       };

        LPDIRECTINPUTEFFECT createdEffect = NULL;

        DIEFFECT effect;
        effect.dwSize = sizeof(DIEFFECT);
        // cartesian coordinate system
        effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        // duration
        effect.dwDuration = (DWORD)(duration * DI_SECONDS);
        // defaults
        effect.dwSamplePeriod = 0;
        effect.dwGain = DI_FFNOMINALMAX;
        effect.dwTriggerButton = DIEB_NOTRIGGER;
        effect.dwTriggerRepeatInterval = 0;
        // x and y axes
        effect.cAxes = 2;
        effect.rgdwAxes = &axes[0];
        // direction
        effect.rglDirection = &dir[0];
        // no envelope
        effect.lpEnvelope = NULL;
        // use the constant force data
        effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
        effect.lpvTypeSpecificParams = &constantForce;
        // start delay
        effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);

        // create the effect
        success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);

        if ((success != DI_OK) || (createdEffect == NULL))
        {
            DXError("Could not create directional constant effect", success);
            return;
        }

        // Store the effect for later use
        effectDatabase[effectType] = createdEffect;
    }

    // play the thing
    if (effectDatabase[effectType])
        success = effectDatabase[effectType]->Start(count, 0);

    if (success != DI_OK)
    {
        // uh-oh, no worky
        DXError("Could not play directional constant effect", success);
    }

    return;
}

void    DXJoystick::ffDirectionalPeriodic(int count, float delay, float duration,
        float x_direction, float y_direction,
        float amplitude, float period, PeriodicType type)
{
    if (!ffHasDirectional())
        return;

    /*
     * Create a constant effect with the specified parameters
     */
    DIPERIODIC periodicForce;

    periodicForce.dwMagnitude = (DWORD)(DI_FFNOMINALMAX * amplitude);
    periodicForce.lOffset = 0;
    periodicForce.dwPhase = 0;
    periodicForce.dwPeriod = (int)(DI_SECONDS * period);

    HRESULT success = DI_OK;

    // Generate a string to identify a specific periodic effect,
    // based on the parameters of the effect
    std::string effectType = TextUtils::format("P%d|%d|%d|%d|%d|%d|%d|%d", count, delay, duration, x_direction, y_direction,
                             amplitude, period, type);

    // Check if we need to create the effect
    EffectMap::iterator itr = effectDatabase.find(effectType);
    if (itr == effectDatabase.end())
    {

        /*
         * Wasn't in effect database, so build it
         */
        DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
        LONG  dir[2] = {(int)(1000.0f * x_direction),
                        (int)(1000.0f * y_direction)
                       };

        LPDIRECTINPUTEFFECT createdEffect = NULL;

        DIEFFECT effect;
        effect.dwSize = sizeof(DIEFFECT);
        // cartesian coordinate system
        effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        // duration
        effect.dwDuration = (DWORD)(duration * DI_SECONDS);
        // defaults
        effect.dwSamplePeriod = 0;
        effect.dwGain = DI_FFNOMINALMAX;
        effect.dwTriggerButton = DIEB_NOTRIGGER;
        effect.dwTriggerRepeatInterval = 0;
        // x and y axes
        effect.cAxes = 2;
        effect.rgdwAxes = &axes[0];
        // direction
        effect.rglDirection = &dir[0];
        // no envelope
        effect.lpEnvelope = NULL;
        // use the constant force data
        effect.cbTypeSpecificParams = sizeof(DIPERIODIC);
        effect.lpvTypeSpecificParams = &periodicForce;
        // start delay
        effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);

        // create the effect
        GUID guid;
        switch (type)
        {
        case BzfJoystick::FF_Sine:
            guid = GUID_Sine;
            break;
        case BzfJoystick::FF_Square:
            guid = GUID_Square;
            break;
        case BzfJoystick::FF_Triangle:
            guid = GUID_Triangle;
            break;
        case BzfJoystick::FF_SawtoothUp:
            guid = GUID_SawtoothUp;
            break;
        case BzfJoystick::FF_SawtoothDown:
            guid = GUID_SawtoothDown;
            break;
        default:
            DXError("Unknown directional periodic effect type", type);
            return;
        }
        success = device->CreateEffect(guid, &effect, &createdEffect, NULL);

        if ((success != DI_OK) || (createdEffect == NULL))
        {
            DXError("Could not create directional periodic effect", success);
            return;
        }

        // Store the effect for later use
        effectDatabase[effectType] = createdEffect;
    }

    // play the thing
    if (effectDatabase[effectType])
        success = effectDatabase[effectType]->Start(count, 0);

    if (success != DI_OK)
    {
        // uh-oh, no worky
        DXError("Could not play directional periodic effect", success);
    }

    return;
}

void    DXJoystick::ffDirectionalResistance(float time, float coefficient,
        float saturation, ResistanceType type)
{
    if (!ffHasDirectional())
        return;

    /*
     * Create a resistance effect with the specified parameters
     */
    DICONDITION resistForce[2];

    resistForce[0].lOffset = 0;
    resistForce[0].lPositiveCoefficient = resistForce[0].lNegativeCoefficient = (int)(10000 * coefficient);
    resistForce[0].dwPositiveSaturation = resistForce[0].dwNegativeSaturation = (int)(10000 * saturation);
    resistForce[0].lDeadBand = 0;

    resistForce[1].lOffset = 0;
    resistForce[1].lPositiveCoefficient = resistForce[1].lNegativeCoefficient = (int)(10000 * coefficient);
    resistForce[1].dwPositiveSaturation = resistForce[1].dwNegativeSaturation = (int)(10000 * saturation);
    resistForce[1].lDeadBand = 0;

    HRESULT success = DI_OK;

    // Generate a string to identify a specific resistance effect,
    // based on the parameters of the effect
    std::string effectType = TextUtils::format("F%d|%d|%d|%d", time, coefficient, saturation, type);

    // Check if we need to create the effect
    EffectMap::iterator itr = effectDatabase.find(effectType);
    if (itr == effectDatabase.end())
    {

        /*
         * Wasn't in effect database, so build it
         */
        DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
        LONG  dir[2] = {1, 1};

        LPDIRECTINPUTEFFECT createdEffect = NULL;

        DIEFFECT effect;
        effect.dwSize = sizeof(DIEFFECT);
        // cartesian coordinate system
        effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
        // duration
        effect.dwDuration = (DWORD)(time * DI_SECONDS);
        // defaults
        effect.dwSamplePeriod = 0;
        effect.dwGain = DI_FFNOMINALMAX;
        effect.dwTriggerButton = DIEB_NOTRIGGER;
        effect.dwTriggerRepeatInterval = 0;
        // x and y axes
        effect.cAxes = 2;
        effect.rgdwAxes = &axes[0];
        // direction
        effect.rglDirection = &dir[0];
        // no envelope
        effect.lpEnvelope = NULL;
        // use the constant force data
        effect.cbTypeSpecificParams = sizeof(DICONDITION) * 2;
        effect.lpvTypeSpecificParams = &resistForce[0];
        // start delay
        effect.dwStartDelay = 0;

        // create the effect
        GUID guid;
        switch (type)
        {
        case BzfJoystick::FF_Position:
            guid = GUID_Spring;
            break;
        case BzfJoystick::FF_Velocity:
            guid = GUID_Damper;
            break;
        case BzfJoystick::FF_Acceleration:
            guid = GUID_Inertia;
            break;
        default:
            DXError("Unknown directional resistance effect type", type);
            return;
        }
        success = device->CreateEffect(guid, &effect, &createdEffect, NULL);

        if ((success != DI_OK) || (createdEffect == NULL))
        {
            DXError("Could not create directional resistance effect", success);
            return;
        }

        // Store the effect for later use
        effectDatabase[effectType] = createdEffect;
    }

    // play the thing
    if (effectDatabase[effectType])
        success = effectDatabase[effectType]->Start(1, 0);

    if (success != DI_OK)
    {
        // uh-oh, no worky
        DXError("Could not play directional resistance effect", success);
    }

    return;
}

bool    DXJoystick::ffHasDirectional() const
{
    /* FIXME: sadly, there's no easy way to figure out what TYPE of
       force feedback a windows joystick supports :( */
    return ffHasRumble();
}

void DXJoystick::enumerateDevices()
{
    if (!directInput)
        return;

    devices.clear();

    HRESULT success = directInput->EnumDevices(DI8DEVCLASS_GAMECTRL,
                      &deviceEnumCallback, NULL,
                      DIEDFL_ATTACHEDONLY);

    if (success != DI_OK)
    {
        DXError("Could not enumerate DirectInput devices", success);
        return;
    }
}

void DXJoystick::reaquireDevice()
{
    if (!device)
        return;

    // try to reaquire the device
    HRESULT success = device->Acquire();

    if (success != DI_OK)
    {
        // couldn't acquire, what to do now?
        device = NULL;
        DXError("Could not acquire device", success);
    }
}

void DXJoystick::resetFF()
{
    if (!device)
        return;

    HRESULT success = device->SendForceFeedbackCommand(DISFFC_RESET);

    if (success != DI_OK)
    {
        // couldn't reset, what to do now?
        device = NULL;
        DXError("Could not reset force feedback device", success);
    }
}

/* error handling */

void DXJoystick::DXError(const char* situation, HRESULT problem)
{
    // uh-oh, no worky
    char buffer[40] = {0};

    // some stuff we can handle
    if (problem == (HRESULT)DIERR_UNPLUGGED)
    {
        device = NULL;
        printError("Joystick device in use has been unplugged.");
        enumerateDevices();
        return;
    }
    if (problem == (HRESULT)DIERR_INPUTLOST)
    {
        reaquireDevice();
        return;
    }
    if (problem == (HRESULT)DIERR_DEVICEFULL)
    {
        printError("DirectInput device is full.  Resetting FF state.");
        resetFF();
        return;
    }

    // print error messages
    if (problem == (HRESULT)DIERR_DEVICENOTREG)
        sprintf(buffer, "Device not registered");
    else if (problem == (HRESULT)DIERR_INVALIDPARAM)
        sprintf(buffer, "Invalid parameter");
    else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
        sprintf(buffer, "Device not initialized");
    else if (problem == (HRESULT)DI_BUFFEROVERFLOW)
        sprintf(buffer, "Buffer overflow");
    else if (problem == (HRESULT)DIERR_BADDRIVERVER)
        sprintf(buffer, "Bad or incompatible device driver");
    else if (problem == (HRESULT)DIERR_EFFECTPLAYING)
        sprintf(buffer, "Effect already playing");
    else if (problem == (HRESULT)DIERR_INCOMPLETEEFFECT)
        sprintf(buffer, "Incomplete effect");
    else if (problem == (HRESULT)DIERR_MOREDATA)
        sprintf(buffer, "Return buffer not large enough");
    else if (problem == (HRESULT)DIERR_NOTACQUIRED)
        sprintf(buffer, "Device not acquired");
    else if (problem == (HRESULT)DIERR_NOTDOWNLOADED)
        sprintf(buffer, "Effect not downloaded");
    else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
        sprintf(buffer, "Device not initialized");
    else if (problem == (HRESULT)DIERR_OUTOFMEMORY)
        sprintf(buffer, "Out of memory");
    else if (problem == (HRESULT)DIERR_UNSUPPORTED)
        sprintf(buffer, "Action not supported by driver");
    else
        sprintf(buffer, "Unknown error (%d)", (int)problem);
    printError(TextUtils::format("%s (%s).", situation, buffer));
}

/* Nasty callbacks 'cause DirectX sucks */

BOOL CALLBACK DXJoystick::deviceEnumCallback(LPCDIDEVICEINSTANCE device, void* UNUSED(pvRef))
{
    if (!device)
        return DIENUM_STOP;

    devices.push_back(*device);

    return DIENUM_CONTINUE;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
